package cn.monkey.transport.netty.server.codec.protocol;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.EmptyByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Arrays;
import java.util.LinkedList;

public class SimpleServerMultipartProtocolSelector extends ChannelInboundHandlerAdapter
        implements ServerMultipartProtocolSelector, ServerProtocolMatcherRepository {
    protected volatile LinkedList<ServerProtocolRouterFunction> protocolMatchers;

    public SimpleServerMultipartProtocolSelector() {
        protocolMatchers = new LinkedList<>();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf in) {
            ByteBuf buffer = Unpooled.copiedBuffer(in);
            if (this.protocolMatchers.isEmpty()) {
                throw new NullPointerException("no protocolMatcher");
            }
            if (buffer instanceof EmptyByteBuf) {
                throw new IllegalArgumentException("empty byteBuf");
            }
            try {
                for (ServerProtocolRouterFunction protocolMatcher : this.protocolMatchers) {
                    if (protocolMatcher.test(ctx.channel(), buffer)) {
                        protocolMatcher.accept(ctx.channel());
                        ctx.pipeline().remove(this);
                        super.channelRead(ctx, msg);
                        return;
                    }
                }
            } finally {
                buffer.release();
            }
            ctx.channel().close();
            throw new UnsupportedByteBufMsgException(buffer);
        }
        super.channelRead(ctx, msg);
    }

    @Override
    public void add(ServerProtocolRouterFunction protocolMatcher, int... port) {
        if (port.length == 0) {
            this.protocolMatchers.add(protocolMatcher);
            return;
        }
        this.protocolMatchers.add(new ServerPortBindingProtocolRouterFunction(protocolMatcher, port));
    }

    @Override
    public void remove(int port) {
        LinkedList<ServerProtocolRouterFunction> protocolMatchers = new LinkedList<>();
        for (ServerProtocolRouterFunction protocolMatcher : this.protocolMatchers) {
            if (protocolMatcher instanceof ServerPortBindingProtocolRouterFunction serverPortBindingProtocolMatcher) {
                int[] ports = serverPortBindingProtocolMatcher.ports();
                ports = Arrays.stream(ports)
                        .filter(p -> p != port)
                        .toArray();
                if (ports.length > 0) {
                    protocolMatchers.add(protocolMatcher);
                }
            }
        }
        this.protocolMatchers = protocolMatchers;
    }

    @Override
    public void remove(Class<ServerProtocolRouterFunction> clazz, int... ports) {
        LinkedList<ServerProtocolRouterFunction> protocolMatchers = new LinkedList<>();
        for (ServerProtocolRouterFunction protocolMatcher : this.protocolMatchers) {
            if (!clazz.equals(protocolMatcher.getClass())) {
                protocolMatchers.add(protocolMatcher);
                continue;
            }
            if (ports.length == 0) {
                continue;
            }
            if (protocolMatcher instanceof ServerPortBindingProtocolRouterFunction serverPortBindingProtocolMatcher) {
                int[] bindingPorts = serverPortBindingProtocolMatcher.ports();
                bindingPorts = Arrays.stream(bindingPorts)
                        .filter(p -> {
                            for (int pp : ports) {
                                if (pp == p) {
                                    return true;
                                }
                            }
                            return false;
                        })
                        .toArray();
                if (bindingPorts.length > 0) {
                    protocolMatchers.add(new ServerPortBindingProtocolRouterFunction(serverPortBindingProtocolMatcher.delegate(), bindingPorts));
                }
            }
        }
        this.protocolMatchers = protocolMatchers;
    }
}